/**
 * Copyright (c) 2009 Teoti Graphix, LLC See:
 *     http://tkarchitecture.teotigraphix.org
 * 
 * Permission is hereby granted, free of charge, to any person
 * obtaining a copy of this software and associated documentation
 * files (the "Software"), to deal in the Software without
 * restriction, including without limitation the rights to use,
 * copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following
 * conditions:
 * 
 * The above copyright notice and this permission notice shall be
 * included in all copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
 * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
 * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
 * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
 * OTHER DEALINGS IN THE SOFTWARE.
 * 
 * @author Michael Schmalle mschmalle@teotigraphix.com
 */
package org.teotigraphix.tka.patterns.behavior
{

import flash.events.IEventDispatcher;

import mx.core.UIComponent;
import mx.events.PropertyChangeEvent;
import mx.styles.IStyleClient;

import org.teotigraphix.dx.core.ToolKitGlobals;
import org.teotigraphix.dx.core.ToolKitUtil;
import org.teotigraphix.dx.interfaces.IBehaviorLayer;
import org.teotigraphix.dx.interfaces.IComponentBehavior;
import org.teotigraphix.dx.interfaces.IComponentBehaviorCore;
import org.teotigraphix.dx.interfaces.ISkinAwareBehavior;
import org.teotigraphix.dx.interfaces.IValidatingBehavior;
import org.teotigraphix.dx.patterns.manager.StyleBindingManager;
import org.teotigraphix.dx.patterns.manager.meta.SkinPartAliasInfo;
import org.teotigraphix.tka.core.tk_internal;

import spark.components.supportClasses.Skin;
import spark.components.supportClasses.SkinnableComponent;

/**
 * The core behavioral strategy of the Component's generalization heirarchy.
 * 
 * <p>When a Component is subclassed to add more heirarchal generalization, 
 * a developer may or may not create a subclass to add new behavior dealing 
 * with the new API at the core level.</p>
 * 
 * <p>This core behavior is responsible for;</p>
 * 
 * <ul>
 * <li>Proxied Component skin attatchment and detachment through
 * the ISkinAwareBehavior interface.</li>
 * <li>Proxied Component validation through the IValidatingBehavior interface.</li>
 * <li>Uses the <code>StyleBindingManager</code> to update property/style
 * bindings.</li>
 * </ul>
 * 
 * <p>The <code>CoreComponentBehavior</code> class also hooks into the host 
 * Component's validation cycle through the <code>IValidatingBehavior</code> 
 * interface. This allows the behavior to take  advantage of the host 
 * <code>UIComponent</code> invalidations.</p>
 * 
 * @copyright Teoti Graphix, LLC
 * @author Michael Schmalle
 * @date 12-22-08, 08-22-09
 */
public class SparkComponentBehaviorCore
	   extends SparkComponentBehavior
	   implements IComponentBehaviorCore,
	 			  ISkinAwareBehavior, IValidatingBehavior
{
//	include "../../core/Version.as"
	
	//--------------------------------------------------------------------------
	//
	//  Protected :: Variables
	//
	//--------------------------------------------------------------------------
	
	/**
	 * The skin instance on the component.
	 */
	protected var skin:Skin;
	
	/**
	 * @private
	 */
	protected var skinChanged:Boolean = true;
	
	/**
	 * @private
	 */
	protected var validateLayerPropertiesFlag:Boolean = true;
	
	/**
	 * @private
	 */
	protected var validateLayerSizeFlag:Boolean = true;
	
	/**
	 * @private
	 */
	protected var validateLayerDisplayListFlag:Boolean = true;
	
	/**
	 * @private
	 */
	protected var validateLayerStyleFlag:Boolean = true;
	
	//--------------------------------------------------------------------------
	//
	//  Public [ComponentAlias] :: Variables
	//
	//--------------------------------------------------------------------------
	
	[ComponentAlias]
	
	/**
	 * The uicomponent interface to the component.
	 */
	public var uicomponent:UIComponent;
	
	//--------------------------------------------------------------------------
	//
	//  Constructor
	//
	//--------------------------------------------------------------------------

	/**
	 * Constructor.
	 */
	public function SparkComponentBehaviorCore()
	{
		super();
	}
		
	//--------------------------------------------------------------------------
	//
	//  IValidateBehavior API :: Methods
	//
	//--------------------------------------------------------------------------
	
	/**
	 * @copy org.teotigraphix.dx.interfaces.IValidatingBehavior#validateProperties()
	 */
	public function validateProperties():void
	{
		if (skinChanged)
		{
			if (skin)
				StyleBindingManager.initialize(skin, IStyleClient(component));
			skinChanged = false;
		}
		
		if (validateLayerPropertiesFlag)
			validateLayerProperties();	
	}
	
	/**
	 * @copy org.teotigraphix.dx.interfaces.IValidatingBehavior#validateSize()
	 */
	public function validateSize():void
	{
		if (validateLayerSizeFlag)
			validateLayerSize();
	}
	
	/**
	 * @copy org.teotigraphix.dx.interfaces.IValidatingBehavior#validateDisplayList()
	 */
	public function validateDisplayList(width:Number, height:Number):void
	{
		if (validateLayerDisplayListFlag)
			validateLayerDisplayList(width, height);
	}
	
	/**
	 * @copy org.teotigraphix.dx.interfaces.IValidatingBehavior#validateStyle()
	 */
	public function validateStyle(styleProp:String):void
	{
		var allStyles:Boolean = styleProp == null || styleProp == "styleName";
		//trace("CoreComponentBehavior::validateStyle(" + styleProp + ")");
		
		if (allStyles || styleProp == "skinClass" || styleProp == "skinFactory")
		{
			skinChanged = true;
			invalidateProperties();
		}
		
		if (ToolKitGlobals.STYLE_BINDING_ENABLED)
		{
			if (component)
				updateStyleMeta(styleProp);		
		}
		
		if (validateLayerStyleFlag)
			validateLayerStyle(styleProp);
	}
	
	//--------------------------------------------------------------------------
	//
	//  Overridden Protected :: Methods
	//
	//--------------------------------------------------------------------------
	
	/**
	 * Adds a <code>PropertyChangeEvent.PROPERTY_CHANGE</code> handler
	 * to the <code>skin</code> and sets the internal <code>skin</code> 
	 * instance.
	 * 
	 * @inheritDoc
	 */
	override protected function skinAdded(skin:IEventDispatcher):void
	{
		super.skinAdded(skin);
		
		skin.addEventListener(
			PropertyChangeEvent.PROPERTY_CHANGE, 
			skin_propertyChangeHandler, false, 0, true);
		
		setSkin(skin as Skin);
	}	
	
	/**
	 * Removes the <code>PropertyChangeEvent.PROPERTY_CHANGE</code> handler
	 * from the <code>skin</code> and unsets the internal <code>skin</code> 
	 * instance.
	 * 
	 * @inheritDoc
	 */
	override protected function skinRemoved(skin:IEventDispatcher):void
	{
		super.skinRemoved(skin);
		
		skin.removeEventListener(
			PropertyChangeEvent.PROPERTY_CHANGE, 
			skin_propertyChangeHandler, false);
		
		setSkin(null);
	}
	
	//--------------------------------------------------------------------------
	//
	//  Protected :: Methods
	//
	//--------------------------------------------------------------------------
	
	/**
	 * @private
	 */
	protected function validateLayerProperties():void
	{
		var layer:IBehaviorLayer = behaviorLayer;
		if (!layer)
			return;
		
		var behaviors:Array = layer.getBehaviors();
		for each(var behavior:IComponentBehavior in behaviors)
		{
			if (behavior is IValidatingBehavior)
				IValidatingBehavior(behavior).validateProperties();
		}
	}
	
	/**
	 * @private
	 */
	protected function validateLayerSize():void
	{
		var layer:IBehaviorLayer = behaviorLayer;
		if (!layer)
			return;
		
		var behaviors:Array = layer.getBehaviors();
		for each(var behavior:IComponentBehavior in behaviors)
		{
			if (behavior is IValidatingBehavior)
				IValidatingBehavior(behavior).validateSize();
		}
	}
	
	/**
	 * @private
	 */
	protected function validateLayerDisplayList(width:Number, height:Number):void
	{
		var layer:IBehaviorLayer = behaviorLayer;
		if (!layer)
			return;
			
		var behaviors:Array = layer.getBehaviors();
		for each(var behavior:IComponentBehavior in behaviors)
		{
			if (behavior is IValidatingBehavior)
				IValidatingBehavior(behavior).validateDisplayList(width, height);
		}
	}
	
	/**
	 * @private
	 */
	protected function validateLayerStyle(styleProp:String):void
	{
		var layer:IBehaviorLayer = behaviorLayer;
		if (!layer)
			return;
		
		var behaviors:Array = layer.getBehaviors();
		for each(var behavior:IComponentBehavior in behaviors)
		{
			if (behavior is IValidatingBehavior)
				IValidatingBehavior(behavior).validateStyle(styleProp);
		}
	}
	
	/**
	 * Updates the <code>component</code>, <code>component.skin</code>, 
	 * <code>component.skin.layout</code>, <code>coreBehavior</code> (this), 
	 * <code>behaviorLayer</code>'s registered behaviors.
	 * 
	 * @param A string style property name to update.
	 */
	protected function updateStyleMeta(styleProp:String):void
	{
		// update [StyleBinding] on the component
		StyleBindingManager.updateStyle(styleProp, component);
		
		var client:IStyleClient = component as IStyleClient;
		
		// update [StyleBinding] on this component's skin
		if(skin)
		{
			StyleBindingManager.updateStyle(styleProp, skin, client);
			// update [StyleBinding] on this component's skin's layout
			if (skin.layout)
				StyleBindingManager.updateStyle(styleProp, skin.layout, client);					
		}
		
		// update [StyleBinding] on this
		StyleBindingManager.updateStyle(styleProp, this, client);
		
		// FIXME (mschmalle) correct behavior layer
		// update [StyleBinding] on this component's behaviorLocator
		var layer:IBehaviorLayer = behaviorLayer;
		if (layer)
		{
			// update [StyleBinding] on this behaviorLocator's behaviors
			var behaviors:Array =	layer.getBehaviors();
			for each(var behavior:IComponentBehavior in behaviors)
			{
				StyleBindingManager.updateStyle(styleProp, behavior, client);
			}
		}
	}
	
	//--------------------------------------------------------------------------
	//
	//  Protected invalidating :: Methods
	//
	//--------------------------------------------------------------------------
	
	/**
	 * Invalidates the {#link #uiComponent}'s properties.
	 */
	protected function invalidateProperties():void
	{
		if (uicomponent)
			uicomponent.invalidateProperties();
	}
	
	/**
	 * Invalidates the {#link #uiComponent}'s size.
	 */
	protected function invalidateSize():void
	{
		if (uicomponent)
			uicomponent.invalidateSize();
	}		
	
	/**
	 * Invalidates the {#link #uiComponent}'s display list.
	 */
	protected function invalidateDisplayList():void
	{
		if (uicomponent)
			uicomponent.invalidateDisplayList();
	}
	
	//--------------------------------------------------------------------------
	//
	//  Protected Skin :: Methods
	//
	//--------------------------------------------------------------------------
	
	/**
	 * Sets the internal skin instance.
	 * 
	 * @param value A Skin instance that will be used as the Component's
	 * skin.
	 */
	protected function setSkin(value:Skin):void
	{
		skin = value as Skin;
	}
	
	/**
	 * @private
	 */
	tk_internal function getSkin():Skin
	{
		return skin;
	}
	
	//--------------------------------------------------------------------------
	//
	//  Private Skin :: Handlers
	//
	//--------------------------------------------------------------------------
	
	/**
	 * @private
	 */
	private function skin_propertyChangeHandler(event:PropertyChangeEvent):void
	{
//		trace("  *** skin_propertyChangeHandler()", event.property)
		propertyChangeHandler(event);
	}
	
	override protected function propertyChangeHandler(event:PropertyChangeEvent):void 
	{
		// FIXME (mschmalle) beta1
		//handleNotification( event.property.toString() );
		super.propertyChangeHandler(event);
		
		//trace("ComponentBehavior.propertyChangeHandler()", event.property)
		// TODO (mschmalle) get rid of SkinnableComponent
		var skin:Skin = SkinnableComponent(component).skin;
		if (!skin)
			return;
		
		// loop through the skin parts declared on this behavior
		// set ref or unset ref if this behavior contains the property
		// part ref sent in the change event
		for each(var part:SkinPartAliasInfo in classMetaData.skinPartAliases) 
		{
			var name:String = part.id;
			// if the property is a skin part
			if(event.property == name)
			{
				// if the skin has the property
				if(name in skin) 
				{
					var api:Class = ToolKitUtil.getDefinition(part.type) as Class;
					if (skin[name] is api) 
					{
						setPart(name, skin[name]);
						partAdded(name, this[name]);
					}
					else
					{
						partRemoved(name, this[name]);
						setPart(name, null);
					}
				}
			}
		}
	}		
}	
}